// Copyright Epic Games, Inc. All Rights Reserved.

#include <workspaces/httpworkspaces.h>

#include <zencore/compactbinarybuilder.h>
#include <zencore/fmtutils.h>
#include <zencore/logging.h>
#include <zencore/trace.h>
#include <zenstore/workspaces.h>
#include <zenutil/basicfile.h>
#include <zenutil/chunkrequests.h>
#include <zenutil/workerpools.h>

#include <unordered_set>

namespace zen {
using namespace std::literals;

ZEN_DEFINE_LOG_CATEGORY_STATIC(LogObj, "fs"sv);

namespace {

	std::filesystem::path GetPathParameter(HttpServerRequest& ServerRequest, std::string_view Name)
	{
		if (std::string_view Value = ServerRequest.GetQueryParams().GetValue(Name); !Value.empty())
		{
			return std::filesystem::path(HttpServerRequest::Decode(Value));
		}
		return {};
	}

	Oid PathToChunkId(const std::filesystem::path& Path)
	{
		const std::string PathBuffer = reinterpret_cast<const char*>(Path.generic_u8string().c_str());
		BLAKE3			  Hash		 = BLAKE3::HashMemory(PathBuffer.data(), PathBuffer.size());
		Hash.Hash[11]				 = 7;  // FIoChunkType::ExternalFile
		return Oid::FromMemory(Hash.Hash);
	}

	constinit AsciiSet ValidAliasCharactersSet{"abcdefghijklmnopqrstuvwxyz0123456789+-_.[]ABCDEFGHIJKLMNOPQRSTUVWXYZ"};

}  // namespace

HttpWorkspacesService::HttpWorkspacesService(HttpStatsService& StatsService, const FileServeConfig& Cfg, Workspaces& Workspaces)
: m_Log(logging::Get("workspaces"))
, m_StatsService(StatsService)
, m_Config(Cfg)
, m_Workspaces(Workspaces)
{
	Initialize();
}

HttpWorkspacesService::~HttpWorkspacesService()
{
	m_StatsService.UnregisterHandler("prj", *this);
}

const char*
HttpWorkspacesService::BaseUri() const
{
	return "/ws/";
}

void
HttpWorkspacesService::HandleRequest(HttpServerRequest& Request)
{
	metrics::OperationTiming::Scope $(m_HttpRequests);

	if (m_Router.HandleRequest(Request) == false)
	{
		ZEN_LOG_WARN(LogObj, "No route found for {0}", Request.RelativeUri());
		return Request.WriteResponse(HttpResponseCode::NotFound, HttpContentType::kText, "Not found"sv);
	}
}

void
HttpWorkspacesService::HandleStatsRequest(HttpServerRequest& HttpReq)
{
	ZEN_TRACE_CPU("WorkspacesService::Stats");
	CbObjectWriter Cbo;

	EmitSnapshot("requests", m_HttpRequests, Cbo);

	Cbo.BeginObject("workspaces");
	{
		Cbo.BeginObject("workspace");
		{
			Cbo << "readcount" << m_WorkspacesStats.WorkspaceReadCount << "writecount" << m_WorkspacesStats.WorkspaceWriteCount
				<< "deletecount" << m_WorkspacesStats.WorkspaceDeleteCount;
		}
		Cbo.EndObject();

		Cbo.BeginObject("workspaceshare");
		{
			Cbo << "readcount" << m_WorkspacesStats.WorkspaceShareReadCount << "writecount" << m_WorkspacesStats.WorkspaceShareWriteCount
				<< "deletecount" << m_WorkspacesStats.WorkspaceShareDeleteCount;
		}
		Cbo.EndObject();

		Cbo.BeginObject("chunk");
		{
			Cbo << "hitcount" << m_WorkspacesStats.WorkspaceShareChunkHitCount << "misscount"
				<< m_WorkspacesStats.WorkspaceShareChunkMissCount;
		}
		Cbo.EndObject();

		Cbo << "filescount" << m_WorkspacesStats.WorkspaceShareFilesReadCount;
		Cbo << "entriescount" << m_WorkspacesStats.WorkspaceShareEntriesReadCount;
		Cbo << "batchcount" << m_WorkspacesStats.WorkspaceShareBatchReadCount;

		Cbo << "requestcount" << m_WorkspacesStats.RequestCount;
		Cbo << "badrequestcount" << m_WorkspacesStats.BadRequestCount;
	}
	Cbo.EndObject();

	return HttpReq.WriteResponse(HttpResponseCode::OK, Cbo.Save());
}

void
HttpWorkspacesService::Initialize()
{
	using namespace std::literals;

	ZEN_LOG_INFO(LogObj, "Initializing Workspaces Service");

	m_StatsService.RegisterHandler("ws", *this);

	m_Router.AddPattern("workspace", "([[:xdigit:]]{24})");
	m_Router.AddPattern("share_id", "([[:xdigit:]]{24})");
	m_Router.AddPattern("chunk", "([[:xdigit:]]{24})");
	m_Router.AddPattern("share_alias", "([[:alnum:]_.\\+\\-\\[\\]]+)");

	m_Router.RegisterRoute(
		"{workspace_id}/{share_id}/files",
		[this](HttpRouterRequest& Req) { FilesRequest(Req); },
		HttpVerb::kGet);

	m_Router.RegisterRoute(
		"{workspace_id}/{share_id}/{chunk}/info",
		[this](HttpRouterRequest& Req) { ChunkInfoRequest(Req); },
		HttpVerb::kGet);

	m_Router.RegisterRoute(
		"{workspace_id}/{share_id}/batch",
		[this](HttpRouterRequest& Req) { BatchRequest(Req); },
		HttpVerb::kPost);

	m_Router.RegisterRoute(
		"{workspace_id}/{share_id}/entries",
		[this](HttpRouterRequest& Req) { EntriesRequest(Req); },
		HttpVerb::kGet);

	m_Router.RegisterRoute(
		"{workspace_id}/{share_id}/{chunk}",
		[this](HttpRouterRequest& Req) { ChunkRequest(Req); },
		HttpVerb::kGet | HttpVerb::kHead);

	m_Router.RegisterRoute(
		"share/{share_alias}/files",
		[this](HttpRouterRequest& Req) { ShareAliasFilesRequest(Req); },
		HttpVerb::kGet);

	m_Router.RegisterRoute(
		"share/{share_alias}/{chunk}/info",
		[this](HttpRouterRequest& Req) { ShareAliasChunkInfoRequest(Req); },
		HttpVerb::kGet);

	m_Router.RegisterRoute(
		"share/{share_alias}/batch",
		[this](HttpRouterRequest& Req) { ShareAliasBatchRequest(Req); },
		HttpVerb::kPost);

	m_Router.RegisterRoute(
		"share/{share_alias}/entries",
		[this](HttpRouterRequest& Req) { ShareAliasEntriesRequest(Req); },
		HttpVerb::kGet);

	m_Router.RegisterRoute(
		"share/{share_alias}/{chunk}",
		[this](HttpRouterRequest& Req) { ShareAliasChunkRequest(Req); },
		HttpVerb::kGet | HttpVerb::kHead);

	m_Router.RegisterRoute(
		"share/{share_alias}",
		[this](HttpRouterRequest& Req) { ShareAliasRequest(Req); },
		HttpVerb::kPut | HttpVerb::kGet | HttpVerb::kDelete);

	m_Router.RegisterRoute(
		"{workspace_id}/{share_id}",
		[this](HttpRouterRequest& Req) { ShareRequest(Req); },
		HttpVerb::kPut | HttpVerb::kGet | HttpVerb::kDelete);

	m_Router.RegisterRoute(
		"{workspace_id}",
		[this](HttpRouterRequest& Req) { WorkspaceRequest(Req); },
		HttpVerb::kPut | HttpVerb::kGet | HttpVerb::kDelete);

	ReadState();
}

std::filesystem::path
HttpWorkspacesService::GetStatePath() const
{
	return m_Config.SystemRootDir / "workspaces";
}

void
HttpWorkspacesService::ReadState()
{
	if (!m_Config.SystemRootDir.empty())
	{
		m_Workspaces.ReadState(GetStatePath(), [](const std::filesystem::path& Path) { return PathToChunkId(Path); });
	}
}

void
HttpWorkspacesService::WriteState()
{
	if (!m_Config.SystemRootDir.empty())
	{
		m_Workspaces.WriteState(GetStatePath());
	}
}

void
HttpWorkspacesService::FilesRequest(HttpRouterRequest& Req)
{
	HttpServerRequest& ServerRequest = Req.ServerRequest();
	const Oid		   WorkspaceId	 = Oid::TryFromHexString(Req.GetCapture(1));
	if (WorkspaceId == Oid::Zero)
	{
		m_WorkspacesStats.BadRequestCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid workspace id '{}'", Req.GetCapture(1)));
	}
	const Oid ShareId = Oid::TryFromHexString(Req.GetCapture(2));
	if (ShareId == Oid::Zero)
	{
		m_WorkspacesStats.BadRequestCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid share id '{}'", Req.GetCapture(2)));
	}
	FilesRequest(Req, WorkspaceId, ShareId);
}

void
HttpWorkspacesService::ChunkInfoRequest(HttpRouterRequest& Req)
{
	HttpServerRequest& ServerRequest = Req.ServerRequest();
	const Oid		   WorkspaceId	 = Oid::TryFromHexString(Req.GetCapture(1));
	if (WorkspaceId == Oid::Zero)
	{
		m_WorkspacesStats.BadRequestCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid workspace id '{}'", Req.GetCapture(1)));
	}
	const Oid ShareId = Oid::TryFromHexString(Req.GetCapture(2));
	if (ShareId == Oid::Zero)
	{
		m_WorkspacesStats.BadRequestCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid share id '{}'", Req.GetCapture(2)));
	}
	const Oid ChunkId = Oid::TryFromHexString(Req.GetCapture(3));
	if (ChunkId == Oid::Zero)
	{
		m_WorkspacesStats.BadRequestCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid chunk id '{}'", Req.GetCapture(3)));
	}
	ChunkInfoRequest(Req, WorkspaceId, ShareId, ChunkId);
}

void
HttpWorkspacesService::BatchRequest(HttpRouterRequest& Req)
{
	HttpServerRequest& ServerRequest = Req.ServerRequest();
	const Oid		   WorkspaceId	 = Oid::TryFromHexString(Req.GetCapture(1));
	if (WorkspaceId == Oid::Zero)
	{
		m_WorkspacesStats.BadRequestCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid workspace id '{}'", Req.GetCapture(1)));
	}
	const Oid ShareId = Oid::TryFromHexString(Req.GetCapture(2));
	if (ShareId == Oid::Zero)
	{
		m_WorkspacesStats.BadRequestCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid share id '{}'", Req.GetCapture(2)));
	}
	BatchRequest(Req, WorkspaceId, ShareId);
}

void
HttpWorkspacesService::EntriesRequest(HttpRouterRequest& Req)
{
	HttpServerRequest& ServerRequest = Req.ServerRequest();
	const Oid		   WorkspaceId	 = Oid::TryFromHexString(Req.GetCapture(1));
	if (WorkspaceId == Oid::Zero)
	{
		m_WorkspacesStats.BadRequestCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid workspace id '{}'", Req.GetCapture(1)));
	}
	const Oid ShareId = Oid::TryFromHexString(Req.GetCapture(2));
	if (ShareId == Oid::Zero)
	{
		m_WorkspacesStats.BadRequestCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid share id '{}'", Req.GetCapture(2)));
	}
	EntriesRequest(Req, WorkspaceId, ShareId);
}

void
HttpWorkspacesService::ChunkRequest(HttpRouterRequest& Req)
{
	HttpServerRequest& ServerRequest = Req.ServerRequest();
	const Oid		   WorkspaceId	 = Oid::TryFromHexString(Req.GetCapture(1));
	if (WorkspaceId == Oid::Zero)
	{
		m_WorkspacesStats.BadRequestCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid workspace id '{}'", Req.GetCapture(1)));
	}
	const Oid ShareId = Oid::TryFromHexString(Req.GetCapture(2));
	if (ShareId == Oid::Zero)
	{
		m_WorkspacesStats.BadRequestCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid share id '{}'", Req.GetCapture(2)));
	}
	const Oid ChunkId = Oid::TryFromHexString(Req.GetCapture(3));
	if (ChunkId == Oid::Zero)
	{
		m_WorkspacesStats.BadRequestCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid chunk id '{}'", Req.GetCapture(3)));
	}
	ChunkRequest(Req, WorkspaceId, ShareId, ChunkId);
}

void
HttpWorkspacesService::ShareRequest(HttpRouterRequest& Req)
{
	HttpServerRequest& ServerRequest = Req.ServerRequest();
	const Oid		   WorkspaceId	 = Oid::TryFromHexString(Req.GetCapture(1));
	if (WorkspaceId == Oid::Zero)
	{
		m_WorkspacesStats.BadRequestCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid workspace id '{}'", Req.GetCapture(1)));
	}
	Oid ShareId = Oid::Zero;
	if (Req.GetCapture(2) != Oid::Zero.ToString())
	{
		ShareId = Oid::TryFromHexString(Req.GetCapture(2));
		if (ShareId == Oid::Zero)
		{
			m_WorkspacesStats.BadRequestCount++;
			return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
											   HttpContentType::kText,
											   fmt::format("Invalid share id '{}'", Req.GetCapture(2)));
		}
	}
	ShareRequest(Req, WorkspaceId, ShareId);
}

void
HttpWorkspacesService::WorkspaceRequest(HttpRouterRequest& Req)
{
	HttpServerRequest& ServerRequest = Req.ServerRequest();
	Oid				   WorkspaceId	 = Oid::TryFromHexString(Req.GetCapture(1));
	switch (ServerRequest.RequestVerb())
	{
		case HttpVerb::kPut:
			{
				std::filesystem::path WorkspacePath = GetPathParameter(ServerRequest, "root_path"sv);
				if (WorkspacePath.empty())
				{
					m_WorkspacesStats.BadRequestCount++;
					return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
													   HttpContentType::kText,
													   "Invalid 'root_path' parameter");
				}
				if (Req.GetCapture(1) == Oid::Zero.ToString())
				{
					// Synthesize Id
					WorkspaceId = PathToChunkId(WorkspacePath);
					ZEN_INFO("Generated workspace id from path '{}': {}", WorkspacePath, WorkspaceId);
				}
				else if (WorkspaceId == Oid::Zero)
				{
					m_WorkspacesStats.BadRequestCount++;
					return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
													   HttpContentType::kText,
													   fmt::format("Invalid workspace id '{}'", Req.GetCapture(1)));
				}
				m_WorkspacesStats.WorkspaceWriteCount++;
				Workspaces::WorkspaceConfiguration NewConfig = {.Id = WorkspaceId, .RootPath = WorkspacePath};
				bool							   OK		 = m_Workspaces.AddWorkspace(NewConfig);
				if (OK)
				{
					WriteState();
					return ServerRequest.WriteResponse(HttpResponseCode::Created, HttpContentType::kText, fmt::format("{}", WorkspaceId));
				}
				else
				{
					Workspaces::WorkspaceConfiguration Config = m_Workspaces.GetWorkspaceConfiguration(WorkspaceId);
					if (Config == NewConfig)
					{
						return ServerRequest.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, fmt::format("{}", WorkspaceId));
					}
					return ServerRequest.WriteResponse(
						HttpResponseCode::Conflict,
						HttpContentType::kText,
						fmt::format("Workspace {} already exists with root path '{}'", WorkspaceId, Config.RootPath));
				}
			}
		case HttpVerb::kGet:
			{
				if (WorkspaceId == Oid::Zero)
				{
					m_WorkspacesStats.BadRequestCount++;
					return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
													   HttpContentType::kText,
													   fmt::format("Invalid workspace id '{}'", Req.GetCapture(1)));
				}
				m_WorkspacesStats.WorkspaceReadCount++;
				Workspaces::WorkspaceInfo Info = m_Workspaces.GetWorkspaceInfo(WorkspaceId);
				if (Info.Config.Id != Oid::Zero)
				{
					CbObjectWriter Response;
					Response << "id" << Info.Config.Id;
					Response << "root_path" << Info.Config.RootPath.string();  // utf8?
					Response.BeginArray("shares");
					for (const Workspaces::WorkspaceShareConfiguration& ShareConfig : Info.Shares)
					{
						Response.BeginObject();
						{
							Response << "id" << ShareConfig.Id;
							Response << "share_path" << ShareConfig.SharePath.string();	 // utf8?
							if (!ShareConfig.Alias.empty())
							{
								Response << "alias" << ShareConfig.Alias;
							}
						}
						Response.EndObject();
					}
					Response.EndArray();

					return ServerRequest.WriteResponse(HttpResponseCode::OK, Response.Save());
				}
				return ServerRequest.WriteResponse(HttpResponseCode::NotFound);
			}
		case HttpVerb::kDelete:
			{
				if (WorkspaceId == Oid::Zero)
				{
					m_WorkspacesStats.BadRequestCount++;
					return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
													   HttpContentType::kText,
													   fmt::format("Invalid workspace id '{}'", Req.GetCapture(1)));
				}
				m_WorkspacesStats.WorkspaceDeleteCount++;
				bool Deleted = m_Workspaces.RemoveWorkspace(WorkspaceId);
				if (Deleted)
				{
					WriteState();
					return ServerRequest.WriteResponse(HttpResponseCode::OK);
				}
				return ServerRequest.WriteResponse(HttpResponseCode::NotFound);
			}
	}
}

void
HttpWorkspacesService::ShareAliasFilesRequest(HttpRouterRequest& Req)
{
	HttpServerRequest& ServerRequest = Req.ServerRequest();
	std::string		   Alias		 = Req.GetCapture(1);
	if (Alias.empty())
	{
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid alias '{}'", Req.GetCapture(1)));
	}
	std::optional<Workspaces::ShareAlias> WorkspaceAndShareId = m_Workspaces.GetShareAlias(Alias);
	if (!WorkspaceAndShareId.has_value())
	{
		return ServerRequest.WriteResponse(HttpResponseCode::NotFound);
	}
	FilesRequest(Req, WorkspaceAndShareId.value().WorkspaceId, WorkspaceAndShareId.value().ShareId);
}

void
HttpWorkspacesService::ShareAliasChunkInfoRequest(HttpRouterRequest& Req)
{
	HttpServerRequest& ServerRequest = Req.ServerRequest();
	std::string		   Alias		 = Req.GetCapture(1);
	if (Alias.empty())
	{
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid alias '{}'", Req.GetCapture(1)));
	}
	std::optional<Workspaces::ShareAlias> WorkspaceAndShareId = m_Workspaces.GetShareAlias(Alias);
	if (!WorkspaceAndShareId.has_value())
	{
		return ServerRequest.WriteResponse(HttpResponseCode::NotFound);
	}
	const Oid ChunkId = Oid::TryFromHexString(Req.GetCapture(2));
	if (ChunkId == Oid::Zero)
	{
		m_WorkspacesStats.BadRequestCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid chunk id '{}'", Req.GetCapture(2)));
	}
	ChunkInfoRequest(Req, WorkspaceAndShareId.value().WorkspaceId, WorkspaceAndShareId.value().ShareId, ChunkId);
}

void
HttpWorkspacesService::ShareAliasBatchRequest(HttpRouterRequest& Req)
{
	HttpServerRequest& ServerRequest = Req.ServerRequest();
	std::string		   Alias		 = Req.GetCapture(1);
	if (Alias.empty())
	{
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid alias '{}'", Req.GetCapture(1)));
	}
	std::optional<Workspaces::ShareAlias> WorkspaceAndShareId = m_Workspaces.GetShareAlias(Alias);
	if (!WorkspaceAndShareId.has_value())
	{
		return ServerRequest.WriteResponse(HttpResponseCode::NotFound);
	}
	BatchRequest(Req, WorkspaceAndShareId.value().WorkspaceId, WorkspaceAndShareId.value().ShareId);
}

void
HttpWorkspacesService::ShareAliasEntriesRequest(HttpRouterRequest& Req)
{
	HttpServerRequest& ServerRequest = Req.ServerRequest();
	std::string		   Alias		 = Req.GetCapture(1);
	if (Alias.empty())
	{
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid alias '{}'", Req.GetCapture(1)));
	}
	std::optional<Workspaces::ShareAlias> WorkspaceAndShareId = m_Workspaces.GetShareAlias(Alias);
	if (!WorkspaceAndShareId.has_value())
	{
		return ServerRequest.WriteResponse(HttpResponseCode::NotFound);
	}
	EntriesRequest(Req, WorkspaceAndShareId.value().WorkspaceId, WorkspaceAndShareId.value().ShareId);
}

void
HttpWorkspacesService::ShareAliasChunkRequest(HttpRouterRequest& Req)
{
	HttpServerRequest& ServerRequest = Req.ServerRequest();
	std::string		   Alias		 = Req.GetCapture(1);
	if (Alias.empty())
	{
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid alias '{}'", Req.GetCapture(1)));
	}
	std::optional<Workspaces::ShareAlias> WorkspaceAndShareId = m_Workspaces.GetShareAlias(Alias);
	if (!WorkspaceAndShareId.has_value())
	{
		return ServerRequest.WriteResponse(HttpResponseCode::NotFound);
	}
	const Oid ChunkId = Oid::TryFromHexString(Req.GetCapture(2));
	if (ChunkId == Oid::Zero)
	{
		m_WorkspacesStats.BadRequestCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid chunk id '{}'", Req.GetCapture(2)));
	}
	ChunkRequest(Req, WorkspaceAndShareId.value().WorkspaceId, WorkspaceAndShareId.value().ShareId, ChunkId);
}

void
HttpWorkspacesService::ShareAliasRequest(HttpRouterRequest& Req)
{
	HttpServerRequest& ServerRequest = Req.ServerRequest();
	std::string		   Alias		 = Req.GetCapture(1);
	if (Alias.empty())
	{
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
										   HttpContentType::kText,
										   fmt::format("Invalid alias '{}'", Req.GetCapture(1)));
	}
	std::optional<Workspaces::ShareAlias> WorkspaceAndShareId = m_Workspaces.GetShareAlias(Alias);
	if (!WorkspaceAndShareId.has_value())
	{
		return ServerRequest.WriteResponse(HttpResponseCode::NotFound);
	}
	ShareRequest(Req, WorkspaceAndShareId.value().WorkspaceId, WorkspaceAndShareId.value().ShareId);
}

void
HttpWorkspacesService::FilesRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& ShareId)
{
	HttpServerRequest& ServerRequest = Req.ServerRequest();

	m_WorkspacesStats.WorkspaceShareFilesReadCount++;

	std::unordered_set<std::string> WantedFieldNames;
	if (auto FieldFilter = HttpServerRequest::Decode(ServerRequest.GetQueryParams().GetValue("fieldnames")); !FieldFilter.empty())
	{
		if (FieldFilter != "*")	 // Get all - empty FieldFilter equal getting all fields
		{
			ForEachStrTok(FieldFilter, ',', [&](std::string_view FieldName) {
				WantedFieldNames.insert(std::string(FieldName));
				return true;
			});
		}
	}
	else
	{
		const bool FilterClient = ServerRequest.GetQueryParams().GetValue("filter"sv) == "client"sv;
		WantedFieldNames.insert("id");
		WantedFieldNames.insert("clientpath");
		if (!FilterClient)
		{
			WantedFieldNames.insert("serverpath");
		}
	}

	bool Refresh = false;
	if (auto RefreshStr = ServerRequest.GetQueryParams().GetValue("refresh"); !RefreshStr.empty())
	{
		Refresh = StrCaseCompare(std::string(RefreshStr).c_str(), "true") == 0;
	}

	const bool WantsAllFields = WantedFieldNames.empty();

	const bool WantsIdField			= WantsAllFields || WantedFieldNames.contains("id");
	const bool WantsClientPathField = WantsAllFields || WantedFieldNames.contains("clientpath");
	const bool WantsServerPathField = WantsAllFields || WantedFieldNames.contains("serverpath");
	const bool WantsRawSizeField	= WantsAllFields || WantedFieldNames.contains("rawsize");
	const bool WantsSizeField		= WantsAllFields || WantedFieldNames.contains("size");

	std::optional<std::vector<Workspaces::ShareFile>> Files =
		m_Workspaces.GetWorkspaceShareFiles(WorkspaceId, ShareId, Refresh, GetSmallWorkerPool());
	if (!Files.has_value())
	{
		return ServerRequest.WriteResponse(HttpResponseCode::NotFound);
	}

	CbObjectWriter Response;
	Response.BeginArray("files"sv);
	{
		for (const Workspaces::ShareFile& Entry : Files.value())
		{
			Response.BeginObject();
			if (WantsIdField)
			{
				Response << "id"sv << Entry.Id;
			}
			if (WantsServerPathField)
			{
				Response << "serverpath"sv << Entry.RelativePath;
			}
			if (WantsClientPathField)
			{
				Response << "clientpath"sv << Entry.RelativePath;
			}
			if (WantsSizeField)
			{
				Response << "size"sv << Entry.Size;
			}
			if (WantsRawSizeField)
			{
				Response << "rawsize"sv << Entry.Size;
			}
			Response.EndObject();
		}
	}
	Response.EndArray();

	return ServerRequest.WriteResponse(HttpResponseCode::OK, Response.Save());
}

void
HttpWorkspacesService::ChunkInfoRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& ShareId, const Oid& ChunkId)
{
	HttpServerRequest&	  ServerRequest = Req.ServerRequest();
	Workspaces::ShareFile File			= m_Workspaces.GetWorkspaceShareChunkInfo(WorkspaceId, ShareId, ChunkId, GetSmallWorkerPool());
	if (File.Id != Oid::Zero)
	{
		CbObjectWriter Response;
		Response << "size"sv << File.Size;
		m_WorkspacesStats.WorkspaceShareChunkHitCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::OK, Response.Save());
	}
	m_WorkspacesStats.WorkspaceShareChunkMissCount++;
	return ServerRequest.WriteResponse(HttpResponseCode::NotFound);
}

void
HttpWorkspacesService::BatchRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& ShareId)
{
	HttpServerRequest&							  ServerRequest = Req.ServerRequest();
	IoBuffer									  Payload		= ServerRequest.ReadPayload();
	std::optional<std::vector<RequestChunkEntry>> ChunkRequests = ParseChunkBatchRequest(Payload);
	if (!ChunkRequests.has_value())
	{
		m_WorkspacesStats.BadRequestCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "batch payload malformed");
	}
	m_WorkspacesStats.WorkspaceShareBatchReadCount++;
	std::vector<Workspaces::ChunkRequest> Requests;
	Requests.reserve(ChunkRequests.value().size());
	std::transform(ChunkRequests.value().begin(),
				   ChunkRequests.value().end(),
				   std::back_inserter(Requests),
				   [](const RequestChunkEntry& Entry) {
					   return Workspaces::ChunkRequest{.ChunkId = Entry.ChunkId, .Offset = Entry.Offset, .Size = Entry.RequestBytes};
				   });
	std::vector<IoBuffer> Chunks = m_Workspaces.GetWorkspaceShareChunks(WorkspaceId, ShareId, Requests, GetSmallWorkerPool());
	if (Chunks.empty())
	{
		return ServerRequest.WriteResponse(HttpResponseCode::NotFound);
	}
	for (const IoBuffer& Buffer : Chunks)
	{
		if (Buffer)
		{
			m_WorkspacesStats.WorkspaceShareChunkHitCount++;
		}
		else
		{
			m_WorkspacesStats.WorkspaceShareChunkMissCount++;
		}
	}
	std::vector<IoBuffer> Response = BuildChunkBatchResponse(ChunkRequests.value(), Chunks);
	if (!Response.empty())
	{
		return ServerRequest.WriteResponse(HttpResponseCode::OK, HttpContentType::kBinary, Response);
	}
	return ServerRequest.WriteResponse(HttpResponseCode::InternalServerError,
									   HttpContentType::kText,
									   fmt::format("failed formatting response for batch of {} chunks", Chunks.size()));
}

void
HttpWorkspacesService::EntriesRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& ShareId)
{
	HttpServerRequest& ServerRequest = Req.ServerRequest();
	std::string_view   OpKey		 = ServerRequest.GetQueryParams().GetValue("opkey"sv);
	if (!OpKey.empty() && OpKey != "file_manifest")
	{
		m_WorkspacesStats.BadRequestCount++;
		return ServerRequest.WriteResponse(HttpResponseCode::NotFound);
	}
	std::unordered_set<std::string> WantedFieldNames;
	if (auto FieldFilter = HttpServerRequest::Decode(ServerRequest.GetQueryParams().GetValue("fieldfilter")); !FieldFilter.empty())
	{
		if (FieldFilter != "*")	 // Get all - empty FieldFilter equal getting all fields
		{
			ForEachStrTok(FieldFilter, ',', [&](std::string_view FieldName) {
				WantedFieldNames.insert(std::string(FieldName));
				return true;
			});
		}
	}

	bool Refresh = false;
	if (auto RefreshStr = ServerRequest.GetQueryParams().GetValue("refresh"); !RefreshStr.empty())
	{
		Refresh = StrCaseCompare(std::string(RefreshStr).c_str(), "true") == 0;
	}

	m_WorkspacesStats.WorkspaceShareEntriesReadCount++;
	std::optional<std::vector<Workspaces::ShareFile>> Files =
		m_Workspaces.GetWorkspaceShareFiles(WorkspaceId, ShareId, Refresh, GetSmallWorkerPool());
	if (!Files.has_value())
	{
		return ServerRequest.WriteResponse(HttpResponseCode::NotFound);
	}
	const bool WantsAllFields = WantedFieldNames.empty();

	const bool WantsIdField			= WantsAllFields || WantedFieldNames.contains("id");
	const bool WantsClientPathField = WantsAllFields || WantedFieldNames.contains("clientpath");
	const bool WantsServerPathField = WantsAllFields || WantedFieldNames.contains("serverpath");

	CbObjectWriter Response;

	if (OpKey.empty())
	{
		Response.BeginArray("entries"sv);
		Response.BeginObject();
	}
	else
	{
		Response.BeginObject("entry"sv);
	}
	{
		// Synthesize a fake op
		Response << "key"
				 << "file_manifest";

		Response.BeginArray("files");
		{
			for (const Workspaces::ShareFile& Entry : Files.value())
			{
				Response.BeginObject();
				{
					if (WantsIdField)
					{
						Response << "id"sv << Entry.Id;
					}
					if (WantsServerPathField)
					{
						Response << "serverpath"sv << Entry.RelativePath;
					}
					if (WantsClientPathField)
					{
						Response << "clientpath"sv << Entry.RelativePath;
					}
				}
				Response.EndObject();
			}
		}
		Response.EndArray();
	}

	if (OpKey.empty())
	{
		Response.EndObject();
		Response.EndArray();
	}
	else
	{
		Response.EndObject();
	}

	return ServerRequest.WriteResponse(HttpResponseCode::OK, Response.Save());
}

void
HttpWorkspacesService::ChunkRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& ShareId, const Oid& ChunkId)
{
	HttpServerRequest& ServerRequest = Req.ServerRequest();

	uint64_t Offset = 0;
	uint64_t Size	= ~(0ull);
	if (auto OffsetParm = ServerRequest.GetQueryParams().GetValue("offset"); OffsetParm.empty() == false)
	{
		if (auto OffsetVal = ParseInt<uint64_t>(OffsetParm))
		{
			Offset = OffsetVal.value();
		}
		else
		{
			m_WorkspacesStats.BadRequestCount++;
			return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
											   HttpContentType::kText,
											   fmt::format("Invalid offset parameter '{}'", OffsetParm));
		}
	}

	if (auto SizeParm = ServerRequest.GetQueryParams().GetValue("size"); SizeParm.empty() == false)
	{
		if (auto SizeVal = ParseInt<uint64_t>(SizeParm))
		{
			Size = SizeVal.value();
		}
		else
		{
			m_WorkspacesStats.BadRequestCount++;
			return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
											   HttpContentType::kText,
											   fmt::format("Invalid size parameter '{}'", SizeParm));
		}
	}

	std::vector<IoBuffer> Response = m_Workspaces.GetWorkspaceShareChunks(
		WorkspaceId,
		ShareId,
		std::vector<Workspaces::ChunkRequest>{Workspaces::ChunkRequest{.ChunkId = ChunkId, .Offset = Offset, .Size = Size}},
		GetSmallWorkerPool());
	if (!Response.empty() && Response[0])
	{
		m_WorkspacesStats.WorkspaceShareChunkHitCount++;
		if (Response[0].GetSize() == 0)
		{
			return ServerRequest.WriteResponse(HttpResponseCode::OK);
		}
		return ServerRequest.WriteResponse(HttpResponseCode::OK, Response[0].GetContentType(), Response);
	}
	m_WorkspacesStats.WorkspaceShareChunkMissCount++;
	return ServerRequest.WriteResponse(HttpResponseCode::NotFound);
}

void
HttpWorkspacesService::ShareRequest(HttpRouterRequest& Req, const Oid& WorkspaceId, const Oid& InShareId)
{
	Oid ShareId = InShareId;

	HttpServerRequest& ServerRequest = Req.ServerRequest();
	switch (ServerRequest.RequestVerb())
	{
		case HttpVerb::kPut:
			{
				std::filesystem::path SharePath = GetPathParameter(ServerRequest, "share_path"sv);
				if (SharePath.empty())
				{
					m_WorkspacesStats.BadRequestCount++;
					return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
													   HttpContentType::kText,
													   "Invalid 'share_path' parameter");
				}

				if (ShareId == Oid::Zero)
				{
					// Synthesize Id
					ShareId = PathToChunkId(SharePath);
					ZEN_INFO("Generated workspace id from path '{}': {}", SharePath, ShareId);
				}

				std::string Alias = HttpServerRequest::Decode(ServerRequest.GetQueryParams().GetValue("alias"sv));
				if (!AsciiSet::HasOnly(Alias, ValidAliasCharactersSet))
				{
					return ServerRequest.WriteResponse(HttpResponseCode::BadRequest, HttpContentType::kText, "Invalid 'alias' parameter");
				}

				m_WorkspacesStats.WorkspaceShareWriteCount++;
				if (m_Workspaces.GetWorkspaceInfo(WorkspaceId).Config.Id != WorkspaceId)
				{
					return ServerRequest.WriteResponse(HttpResponseCode::NotFound,
													   HttpContentType::kText,
													   fmt::format("Workspace '{}' does not exist", WorkspaceId));
				}
				const Workspaces::WorkspaceShareConfiguration NewConfig = {.Id		  = ShareId,
																		   .SharePath = SharePath,
																		   .Alias	  = std::string(Alias)};
				bool OK = m_Workspaces.AddWorkspaceShare(WorkspaceId, NewConfig, [](const std::filesystem::path& Path) {
					return PathToChunkId(Path);
				});
				if (OK)
				{
					WriteState();
					return ServerRequest.WriteResponse(HttpResponseCode::Created, HttpContentType::kText, fmt::format("{}", ShareId));
				}
				else
				{
					Workspaces::WorkspaceShareConfiguration Config = m_Workspaces.GetWorkspaceShareConfiguration(WorkspaceId, ShareId);
					if (Config == NewConfig)
					{
						return ServerRequest.WriteResponse(HttpResponseCode::OK, HttpContentType::kText, fmt::format("{}", ShareId));
					}
					return ServerRequest.WriteResponse(
						HttpResponseCode::Conflict,
						HttpContentType::kText,
						fmt::format("Workspace share '{}' already exist in workspace '{}' with share path '{}' and alias '{}'",
									ShareId,
									WorkspaceId,
									Config.SharePath,
									Config.Alias));
				}
			}
		case HttpVerb::kGet:
			{
				if (WorkspaceId == Oid::Zero)
				{
					m_WorkspacesStats.BadRequestCount++;
					return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
													   HttpContentType::kText,
													   fmt::format("Invalid workspace id '{}'", Req.GetCapture(1)));
				}
				if (ShareId == Oid::Zero)
				{
					m_WorkspacesStats.BadRequestCount++;
					return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
													   HttpContentType::kText,
													   fmt::format("Invalid share id '{}'", ShareId));
				}
				m_WorkspacesStats.WorkspaceShareReadCount++;
				Workspaces::WorkspaceShareConfiguration Config = m_Workspaces.GetWorkspaceShareConfiguration(WorkspaceId, ShareId);
				if (Config.Id != Oid::Zero)
				{
					CbObjectWriter Response;
					Response << "id" << Config.Id;
					Response << "share_path" << Config.SharePath.string();	// utf8?
					if (!Config.Alias.empty())
					{
						Response << "alias" << Config.Alias;
					}
					return ServerRequest.WriteResponse(HttpResponseCode::OK, Response.Save());
				}
				return ServerRequest.WriteResponse(HttpResponseCode::NotFound);
			}
		case HttpVerb::kDelete:
			{
				if (WorkspaceId == Oid::Zero)
				{
					m_WorkspacesStats.BadRequestCount++;
					return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
													   HttpContentType::kText,
													   fmt::format("Invalid workspace id '{}'", Req.GetCapture(1)));
				}
				if (ShareId == Oid::Zero)
				{
					m_WorkspacesStats.BadRequestCount++;
					return ServerRequest.WriteResponse(HttpResponseCode::BadRequest,
													   HttpContentType::kText,
													   fmt::format("Invalid share id '{}'", ShareId));
				}
				m_WorkspacesStats.WorkspaceShareDeleteCount++;
				bool Deleted = m_Workspaces.RemoveWorkspaceShare(WorkspaceId, ShareId);
				if (Deleted)
				{
					WriteState();
					return ServerRequest.WriteResponse(HttpResponseCode::OK);
				}
				return ServerRequest.WriteResponse(HttpResponseCode::NotFound);
			}
	}
}

}  // namespace zen
